﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.LCS.AIDCCodes.QrCode
{
    #region ReedSolomonEncoder
    internal sealed class ReedSolomonEncoder
    {
        private GF256 field;
        private System.Collections.ArrayList cachedGenerators;
        internal ReedSolomonEncoder(GF256 field)
        {
            if (!GF256.QR_CODE_FIELD.Equals(field))
                throw new System.ArgumentException("Only QR Code is supported at this time");
            
            this.field = field;
            this.cachedGenerators = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
            cachedGenerators.Add(new GF256Poly(field, new int[] { 1 }));
        }
        private GF256Poly buildGenerator(int degree)
        {
            if (degree >= cachedGenerators.Count)
            {
                GF256Poly lastGenerator = (GF256Poly)cachedGenerators[cachedGenerators.Count - 1];
                for (int d = cachedGenerators.Count; d <= degree; d++)
                {
                    GF256Poly nextGenerator = lastGenerator.multiply(new GF256Poly(field, new int[] { 1, field.exp(d - 1) }));
                    cachedGenerators.Add(nextGenerator);
                    lastGenerator = nextGenerator;
                }
            }
            return (GF256Poly)cachedGenerators[degree];
        }
        internal void encode(int[] toEncode, int ecBytes)
        {
            if (ecBytes == 0)
                throw new System.ArgumentException("No error correction bytes");
            
            int dataBytes = toEncode.Length - ecBytes;
            if (dataBytes <= 0)
                throw new System.ArgumentException("No data bytes provided");
            
            GF256Poly generator = buildGenerator(ecBytes);
            int[] infoCoefficients = new int[dataBytes];
            Array.Copy(toEncode, 0, infoCoefficients, 0, dataBytes);
            GF256Poly info = new GF256Poly(field, infoCoefficients);
            info = info.multiplyByMonomial(ecBytes, 1);
            GF256Poly remainder = info.divide(generator)[1];
            int[] coefficients = remainder.Coefficients;
            int numZeroCoefficients = ecBytes - coefficients.Length;
            for (int i = 0; i < numZeroCoefficients; i++)
            {
                toEncode[dataBytes + i] = 0;
            }
            Array.Copy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.Length);
        }
    }
    #endregion
    #region GF256
    /// <summary>
    /// <p>This class contains utility methods for performing mathematical operations over
    /// the Galois Field GF(256). Operations use a given primitive polynomial in calculations.
    /// </p>
    /// <p>
    /// Throughout this package, elements of GF(256) are represented as an <code>int</code>
    /// for convenience and speed (but at the cost of memory).
    /// Only the bottom 8 bits are really used.</p>
    /// </summary>
    internal sealed class GF256
    {
        internal GF256Poly Zero { get { return zero; } }
        internal GF256Poly One { get { return one; } }

        internal static readonly GF256 QR_CODE_FIELD = new GF256(0x011D);         // x^8 + x^4 + x^3 + x^2 + 1
        internal static readonly GF256 DATA_MATRIX_FIELD = new GF256(0x012D);     // x^8 + x^5 + x^3 + x^2 + 1
        private int[] expTable;
        private int[] logTable;
        private GF256Poly zero;
        private GF256Poly one;
        /// <summary>
        /// Create a representation of GF(256) using the given primitive polynomial.
        /// </summary>
        /// <param name="primitive">irreducible polynomial whose coefficients are represented by
        /// the bits of an int, where the least-significant bit represents the constant
        /// coefficient
        /// </param>
        private GF256(int primitive)
        {
            expTable = new int[256];
            logTable = new int[256];
            int x = 1;
            for (int i = 0; i < 256; i++)
            {
                expTable[i] = x;
                x <<= 1; // x = x * 2; we're assuming the generator alpha is 2
                if (x >= 0x100)
                {
                    x ^= primitive;
                }
            }
            for (int i = 0; i < 255; i++)
            {
                logTable[expTable[i]] = i;
            }
            // logTable[0] == 0 but this should never be used
            zero = new GF256Poly(this, new int[] { 0 });
            one = new GF256Poly(this, new int[] { 1 });
        }
        /// <returns> the monomial representing coefficient * x^degree
        /// </returns>
        internal GF256Poly BuildMonomial(int degree, int coefficient)
        {
            if (degree < 0)
                throw new System.ArgumentException("GF256.BuildMonomial(): \"degree\" can not be negative value.");
            
            if (coefficient == 0)
                return zero;
            
            int[] coefficients = new int[degree + 1];
            coefficients[0] = coefficient;
            return new GF256Poly(this, coefficients);
        }
        /// <summary> Implements both addition and subtraction -- they are the same in GF(256).
        /// 
        /// </summary>
        /// <returns> sum/difference of a and b
        /// </returns>
        internal static int addOrSubtract(int a, int b)
        {
            return a ^ b;
        }
        /// <returns> 2 to the power of a in GF(256)
        /// </returns>
        internal int exp(int a)
        {
            return expTable[a];
        }
        /// <returns> base 2 log of a in GF(256)
        /// </returns>
        internal int log(int a)
        {
            if (a == 0)
                throw new System.ArgumentException("GF256.log(a): \"a\" can not be 0.");
            
            return logTable[a];
        }
        /// <returns> multiplicative inverse of a
        /// </returns>
        internal int inverse(int a)
        {
            if (a == 0)
                throw new System.ArithmeticException("GF256.inverse(a): \"a\" can not be 0.");
            
            return expTable[255 - logTable[a]];
        }
        /// <summary>
        /// Return a product of (a) and (b) in GF(256)
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal int multiply(int a, int b)
        {
            if (a == 0 || b == 0) return 0;
            if (a == 1) return b;
            if (b == 1) return a;
            return expTable[(logTable[a] + logTable[b]) % 255];
        }
    }
    #endregion
    #region GF256Poly
    /// <summary>
    /// <p>Represents a polynomial whose coefficients are elements of GF(256).
    /// Instances of this class are immutable.
    /// </p>
    /// <p>
    /// Much credit is due to William Rucklidge since portions of this code are an indirect
    /// port of his C++ Reed-Solomon implementation.
    /// </p>
    /// </summary>
    sealed class GF256Poly
    {
        internal int[] Coefficients { get { return coefficients; } }
        /// <returns>
        /// degree of this polynomial
        /// </returns>
        internal int Degree { get { return coefficients.Length - 1; } }
        /// <returns>
        /// true if this polynomial is the monomial "0"
        /// </returns>
        internal bool IsZero { get { return coefficients[0] == 0; }         }
        private GF256 field;
        private int[] coefficients;
        /// <summary>
        /// or if leading coefficient is 0 and this is not a
        /// constant polynomial (that is, it is not the monomial "0")
        /// </summary>
        /// <param name="field">the {@link GF256} instance representing the field to use
        /// to perform computations
        /// </param>
        /// <param name="coefficients">coefficients as ints representing elements of GF(256), arranged
        /// from most significant (highest-power term) coefficient to least significant
        /// </param>
        /// <throws>  IllegalArgumentException if argument is null or empty, </throws>
        internal GF256Poly(GF256 field, int[] coefficients)
        {
            if (coefficients == null || coefficients.Length == 0)
                throw new System.ArgumentException("GF256Poly: \"coefficients\" can not be a empty array.");
            
            this.field = field;
            int coefficientsLength = coefficients.Length;
            if (coefficientsLength > 1 && coefficients[0] == 0)
            {
                // Leading term must be non-zero for anything except the constant polynomial "0"
                int firstNonZero = 1;
                while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0)
                {
                    firstNonZero++;
                }
                if (firstNonZero == coefficientsLength)
                {
                    this.coefficients = field.Zero.coefficients;
                }
                else
                {
                    this.coefficients = new int[coefficientsLength - firstNonZero];
                    Array.Copy(coefficients, firstNonZero, this.coefficients, 0, this.coefficients.Length);
                }
            }
            else
            {
                this.coefficients = coefficients;
            }
        }

        /// <returns>
        /// coefficient of x^degree term in this polynomial
        /// </returns>
        internal int getCoefficient(int degree)
        {
            return coefficients[coefficients.Length - 1 - degree];
        }
        /// <returns>
        /// evaluation of this polynomial at a given point
        /// </returns>
        internal int evaluateAt(int a)
        {
            if (a == 0)
            {
                // Just return the x^0 coefficient
                return getCoefficient(0);
            }
            int size = coefficients.Length;
            if (a == 1)
            {
                // Just the sum of the coefficients
                int result = 0;
                for (int i = 0; i < size; i++)
                {
                    result = GF256.addOrSubtract(result, coefficients[i]);
                }
                return result;
            }
            int result2 = coefficients[0];
            for (int i = 1; i < size; i++)
            {
                result2 = GF256.addOrSubtract(field.multiply(a, result2), coefficients[i]);
            }
            return result2;
        }

        internal GF256Poly addOrSubtract(GF256Poly other)
        {
            if (!field.Equals(other.field))
            {
                throw new System.ArgumentException("GF256Polys do not have same GF256 field");
            }
            if (IsZero)
            {
                return other;
            }
            if (other.IsZero)
            {
                return this;
            }

            int[] smallerCoefficients = this.coefficients;
            int[] largerCoefficients = other.coefficients;
            if (smallerCoefficients.Length > largerCoefficients.Length)
            {
                int[] temp = smallerCoefficients;
                smallerCoefficients = largerCoefficients;
                largerCoefficients = temp;
            }
            int[] sumDiff = new int[largerCoefficients.Length];
            int lengthDiff = largerCoefficients.Length - smallerCoefficients.Length;
            // Copy high-order terms only found in higher-degree polynomial's coefficients
            Array.Copy(largerCoefficients, 0, sumDiff, 0, lengthDiff);

            for (int i = lengthDiff; i < largerCoefficients.Length; i++)
            {
                sumDiff[i] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
            }

            return new GF256Poly(field, sumDiff);
        }

        internal GF256Poly multiply(GF256Poly other)
        {
            if (!field.Equals(other.field))
            {
                throw new System.ArgumentException("GF256Polys do not have same GF256 field");
            }
            if (IsZero || other.IsZero)
            {
                return field.Zero;
            }
            int[] aCoefficients = this.coefficients;
            int aLength = aCoefficients.Length;
            int[] bCoefficients = other.coefficients;
            int bLength = bCoefficients.Length;
            int[] product = new int[aLength + bLength - 1];
            for (int i = 0; i < aLength; i++)
            {
                int aCoeff = aCoefficients[i];
                for (int j = 0; j < bLength; j++)
                {
                    product[i + j] = GF256.addOrSubtract(product[i + j], field.multiply(aCoeff, bCoefficients[j]));
                }
            }
            return new GF256Poly(field, product);
        }

        internal GF256Poly multiply(int scalar)
        {
            if (scalar == 0)
            {
                return field.Zero;
            }
            if (scalar == 1)
            {
                return this;
            }
            int size = coefficients.Length;
            int[] product = new int[size];
            for (int i = 0; i < size; i++)
            {
                product[i] = field.multiply(coefficients[i], scalar);
            }
            return new GF256Poly(field, product);
        }

        internal GF256Poly multiplyByMonomial(int degree, int coefficient)
        {
            if (degree < 0)
            {
                throw new System.ArgumentException("GF256Poly.multiplyByMonomial(): \"degree\" can not be negative value.");
            }
            if (coefficient == 0)
            {
                return field.Zero;
            }
            int size = coefficients.Length;
            int[] product = new int[size + degree];
            for (int i = 0; i < size; i++)
            {
                product[i] = field.multiply(coefficients[i], coefficient);
            }
            return new GF256Poly(field, product);
        }

        internal GF256Poly[] divide(GF256Poly other)
        {
            if (!field.Equals(other.field))
            {
                throw new System.ArgumentException("GF256Polys do not have same GF256 field");
            }
            if (other.IsZero)
            {
                throw new System.ArgumentException("Divide by 0");
            }

            GF256Poly quotient = field.Zero;
            GF256Poly remainder = this;

            int denominatorLeadingTerm = other.getCoefficient(other.Degree);
            int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);

            while (remainder.Degree >= other.Degree && !remainder.IsZero)
            {
                int degreeDifference = remainder.Degree - other.Degree;
                int scale = field.multiply(remainder.getCoefficient(remainder.Degree), inverseDenominatorLeadingTerm);
                GF256Poly term = other.multiplyByMonomial(degreeDifference, scale);
                GF256Poly iterationQuotient = field.BuildMonomial(degreeDifference, scale);
                quotient = quotient.addOrSubtract(iterationQuotient);
                remainder = remainder.addOrSubtract(term);
            }

            return new GF256Poly[] { quotient, remainder };
        }

        public override System.String ToString()
        {
            System.Text.StringBuilder result = new System.Text.StringBuilder(8 * Degree);
            for (int degree = Degree; degree >= 0; degree--)
            {
                int coefficient = getCoefficient(degree);
                if (coefficient != 0)
                {
                    if (coefficient < 0)
                    {
                        result.Append(" - ");
                        coefficient = -coefficient;
                    }
                    else
                    {
                        if (result.Length > 0)
                        {
                            result.Append(" + ");
                        }
                    }
                    if (degree == 0 || coefficient != 1)
                    {
                        int alphaPower = field.log(coefficient);
                        if (alphaPower == 0)
                        {
                            result.Append('1');
                        }
                        else if (alphaPower == 1)
                        {
                            result.Append('a');
                        }
                        else
                        {
                            result.Append("a^");
                            result.Append(alphaPower);
                        }
                    }
                    if (degree != 0)
                    {
                        if (degree == 1)
                        {
                            result.Append('x');
                        }
                        else
                        {
                            result.Append("x^");
                            result.Append(degree);
                        }
                    }
                }
            }
            return result.ToString();
        }
    }
	#endregion
}
